Paypal 支付功能的 C# .NET / JS 實現


說明

最近用到了 Paypal 支付功能,英語一般般的我也不得不硬着頭皮踩一踩這樣的坑。經過近乎半個月的作,終於實現了簡單的支付功能,那么首先就說說使用 Paypal 必定要知道的幾點(當前日期 2018年08月07日):

1. 你應該知道 Paypal 支付功能是支持銀聯卡的,但是不支持中國買家賬號支付給中國賣家賬號

2. Paypal 接口有兩套,切記,產品環境和 sandbox 測試環境不同

3. 測試賬號同樣不能使用中國賬號給中國賬號付款

4. 如果你僅僅想具有固定金額的支付按鈕,用你的 Paypal 商家賬號登錄官網,配置頁里面完全可以配置出固定的支付按鈕,然后 Copy 對應的 Html 到你的頁面就 OK 了,也就沒有必要通過更復雜的方式去支付了

image

5. 如果你必須動態價格和商品信息、或者你要學習基本的 Paypal 接口的話,那么就請靜靜的往下看吧

6. 真實環境支付 Paypal 每一筆都需要收取商家賬號手續費的,並且手續費不低,如果你用真實環境測試,那么一定要記得每一筆都申請退款吧,退款很方便,商家后台就能直接發起,退款幾乎是實時的。

Paypal 費用說明:https://www.paypal.com/businesswallet/fees/paypal-fees

image

image

 

相關資料

Paypal 官方地址:https://www.paypal.com/

Paypal 官方測試地址:https://www.sandbox.paypal.com

Paypal 開發者中心:https://developer.paypal.com/

Paypal API: https://api.paypal.com

Paypal sandbox API: https://api.sandbox.paypal.com

 

Paypal Checkout JS 支付模式

模式圖片:

 image

模式說明:

Checkout JS 模式是一種前端實現,使用官方提供的 Checkout.js SDK 實現支付,不需要自己寫直接調用接口的代碼,相對而言也挺簡單,但是如果你想檢測支付是否成功,你應當通過調用接口的方式驗證了。

支付部分代碼:

<div id="paypal-button"></div>
<script src="https://www.paypalobjects.com/api/checkout.js"></script>
<script type="text/javascript">
    paypal.Button.render({
        env: 'production', // production or sandbox 表示產品環境還是測試環境
        client: {
            production: '', // 產品環境,值為字符串,配置實際商家號的 ClientId
            // sandbox: '', // 測試環境,值為字符串,配置商家測試號的 ClientId
        },
        style: {
            size: 'medium',
            color: 'black',
            shape: 'pill',
            label: 'paypal',
            tagline: 'false',
            fundingicons: 'true'
        },
        commit: true,
        payment: function (data, actions) {
            return actions.payment.create({
                transactions: [
                    {
                        amount: {
                            total: "0.01",
                            currency: "USD"
                        },
                        description: "測試商品描述",
                        custom: "X00002"
                    }
                ],
                redirect_urls: {
                    return_url: 'http://localhost:4478/Success.aspx?type=js',
                    cancel_url: 'http://localhost:4478/Cancel.aspx'
                }
            });
        },
        onAuthorize: function (data, actions) {
            return actions.payment.execute()
                .then(function () {
                    actions.redirect();
                });
        },
        onCancel: function (data, actions) {
            actions.redirect();
        }
    }, '#paypal-button');
</script>

如果你需要在支付跳轉的成功頁再次驗證一下是否支付成功,你需要自己調用官方提供的 RESTful API,參見下文的 RESTful API 支付模式

RESTful API 支付模式

說明

接口的方式很常見,和支付寶的接口類似,只是使用了 RESTful API 的模式,采用了 Basic Auth 的加密方式。使用接口的模式很常規,我們在頁面點擊按鈕調用支付接口,彈出支付頁,支付成功跳轉到成功頁面,成功頁面再調用確認支付接口確認結果。

支付接口調用:

using System;
using System.Text;
using System.Web.Script.Serialization;
using cn.lovelong.Paypal.Config;
using cn.lovelong.Paypal.Enums;
using cn.lovelong.Paypal.Model;

namespace cn.lovelong.Paypal.Paypal
{
    /// <summary>
    /// CreatePayment 的摘要說明
    /// </summary>
    public class CreatePayment
    {
        public CreatePayment()
        {
        }

        public PaymentResult Pay(string json)
        {
            var jsonResult = HttpHelper.PostJson(
                UrlConfig.CreatePaymentUrl, 
                AccountConfig.ClientId, AccountConfig.Secret, json,
                Encoding.UTF8);
            var result = new JavaScriptSerializer().Deserialize<PaymentResult>(jsonResult);
            return result;
        }

        public PaymentResult Pay(PaymentParam param)
        {
            var json = GetPayParams(param);
            return Pay(json);
        }
        
        public string GetPayParams(PaymentParam param)
        {
            var total = param.Total.ToString("N");
            var currency = Enum.GetName(typeof (PaypalCurrency), param.Currency);
            var payParams = new
            {
                intent = "sale",
                redirect_urls = new
                {
                    return_url = param.ReturnUrl,
                    cancel_url = param.CancelUrl,
                },
                payer = new
                {
                    payment_method = "paypal"
                },
                transactions = new dynamic[]
                {
                    new
                    {
                        amount = new
                        {
                            total = total,
                            currency = currency
                        },
                        description = param.Description,
                        custom = param.Code,
                        item_list = new
                        {
                            items = new dynamic[]
                            {
                                new
                                {
                                    name = param.Name,
                                    //description = param.Name,
                                    quantity = "1",
                                    price = total,
                                    //tax = "0.01",
                                    //sku = "1",
                                    currency = currency
                                }
                            }
                        }
                    }
                }
            };
            var json = new JavaScriptSerializer().Serialize(payParams);
            return json;
        }

        public string GetFullPayParams(decimal total, PaypalCurrency currency, string returnUrl, string cancelUrl)
        {
            var payParams = new
            {
                intent = "sale",
                redirect_urls = new
                {
                    return_url = returnUrl,
                    cancel_url = cancelUrl,
                },
                payer = new
                {
                    payment_method = "paypal"
                },
                transactions = new dynamic[]
                {
                    new
                    {
                        amount = new
                        {
                            total = total.ToString("N"),
                            currency = Enum.GetName(typeof(PaypalCurrency),currency),
                            details = new
                            {
                                subtotal = "30.00",
                                tax = "0.07",
                                shipping = "0.03",
                                handling_fee = "1.00",
                                shipping_discount = "-1.00",
                                insurance = "0.01"
                            }
                        },
                        description = "",
                        custom = "EBAY_EMS_90048630024435",
                        invoice_number = "48787589673",
                        payment_options = new
                        {
                            allowed_payment_method = "INSTANT_FUNDING_SOURCE"
                        },
                        soft_descriptor = "ECHI5786786",
                        item_list = new
                        {
                            items = new dynamic[]
                            {
                                new
                                {
                                    name = "hat",
                                    description = "Brown hat.",
                                    quantity = "5",
                                    price = "3",
                                    tax = "0.01",
                                    sku = "1",
                                    currency = "USD"
                                }
                            },
                            shipping_address = new
                            {
                                recipient_name = "Brian Robinson",
                                line1 = "4th Floor",
                                line2 = "Unit #34",
                                city = "San Jose",
                                country_code = "US",
                                postal_code = "95131",
                                phone = "011862212345678",
                                state = "CA"
                            },
                        }
                    }
                }
            };
            var json = new JavaScriptSerializer().Serialize(payParams);
            return json;
        }
    }
}

確認支付接口:

using System.Text;
using System.Web.Script.Serialization;
using cn.lovelong.Paypal.Config;
using cn.lovelong.Paypal.Model;

namespace cn.lovelong.Paypal.Paypal
{
    /// <summary>
    /// Approved 的摘要說明
    /// </summary>
    public class Approved
    {
        public PaymentResult DoJson(string paymentId, dynamic json)
        {
            var jsonResult = HttpHelper.PostJson(string.Format(UrlConfig.ApprovedUrl, paymentId), 
                AccountConfig.ClientId, AccountConfig.Secret, json, Encoding.UTF8);
            var result = new JavaScriptSerializer().Deserialize<PaymentResult>(jsonResult);
            return result;
        }

        public PaymentResult Do(string paymentId, string payerId)
        {
            var json = GetPayParams(payerId);
            return DoJson(paymentId, json);
        }

        public string GetPayParams(string payerId)
        {
            var payParams = new
            {
                payer_id = payerId
            };
            var json = new JavaScriptSerializer().Serialize(payParams);
            return json;
        }
    }
}

查詢支付結果接口調用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Script.Serialization;
using cn.lovelong.Paypal.Config;
using cn.lovelong.Paypal.Model;

namespace cn.lovelong.Paypal.Paypal
{
    public class ShowPaymentDetails
    {
        public PaymentResult Do(string paymentId)
        {
            var json = HttpHelper.Get(
                string.Format(UrlConfig.ShowPaymentDetailsUrl, paymentId), 
                AccountConfig.ClientId, AccountConfig.Secret,
                Encoding.UTF8);
            var result = new JavaScriptSerializer().Deserialize<PaymentResult>(json);
            return result;
        }
    }
}

最容易出問題的反而是通用類 HttpHelper:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;

namespace cn.lovelong.Paypal
{
    public class HttpHelper
    {
        public static string PostForm(string url, string userName, string password, Dictionary<string,object> dic, Encoding encoding)
        {
            var param = string.Empty;
            foreach (var o in dic)
            {
                if (string.IsNullOrEmpty(param))
                    param += o.Key + "=" + o.Value;
                else
                    param += "&" + o.Key + "=" + o.Value;
            }
            byte[] byteArray = encoding.GetBytes(param);

            //處理HttpWebRequest訪問https有安全證書的問題( 請求被中止: 未能創建 SSL/TLS 安全通道。)
            ServicePointManager.ServerCertificateValidationCallback += (s, cert, chain, sslPolicyErrors) => true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
            
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(encoding.GetBytes(userName + ":" + password)));
            request.PreAuthenticate = true;

            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = byteArray.Length;

            //寫入參數
            Stream newStream = request.GetRequestStream();
            newStream.Write(byteArray, 0, byteArray.Length);
            newStream.Close();

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                using (var stream = response.GetResponseStream())
                {
                    if(stream != null)
                    using (StreamReader sr = new StreamReader(stream, Encoding.UTF8))
                    {
                        return sr.ReadToEnd();
                    }
                }
            }
            return string.Empty;
        }

        public static string PostJson(string url, string userName, string password, string json, Encoding encoding)
        {
            byte[] byteArray = encoding.GetBytes(json);

            //處理HttpWebRequest訪問https有安全證書的問題( 請求被中止: 未能創建 SSL/TLS 安全通道。)
            ServicePointManager.ServerCertificateValidationCallback += (s, cert, chain, sslPolicyErrors) => true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 |
                                                    SecurityProtocolType.Tls;

            HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
            request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(encoding.GetBytes(userName + ":" + password)));
            request.PreAuthenticate = true;

            request.Method = "POST";
            request.Headers.Add("Cache-Control", "no-cache");
            request.ContentType = "application/json";
            request.ContentLength = byteArray.Length;

            //寫入參數
            Stream newStream = request.GetRequestStream();
            newStream.Write(byteArray, 0, byteArray.Length);
            newStream.Close();

            using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
            {
                using (var stream = response.GetResponseStream())
                {
                    if (stream != null)
                        using (StreamReader sr = new StreamReader(stream, Encoding.UTF8))
                        {
                            return sr.ReadToEnd();
                        }
                }
            }
            return string.Empty;
        }
        
        public static string Get(string url, string userName, string password, Encoding encoding)
        {
            //處理HttpWebRequest訪問https有安全證書的問題( 請求被中止: 未能創建 SSL/TLS 安全通道。)
            ServicePointManager.ServerCertificateValidationCallback += (s, cert, chain, sslPolicyErrors) => true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 |
                                                    SecurityProtocolType.Tls;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(encoding.GetBytes(userName + ":" + password)));
            request.PreAuthenticate = true;

            request.Method = "GET";
            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                using (var stream = response.GetResponseStream())
                {
                    if (stream != null)
                        using (StreamReader sr = new StreamReader(stream, Encoding.UTF8))
                        {
                            return sr.ReadToEnd();
                        }
                }
            }
            return string.Empty;
        }
    }
}
主要的功能都已經實現了!看看演示 Demo 吧!
1. 支付頁面
image 
2. Checkout JS 方式(如果你的頁面點擊登錄之后一直在第二個頁面轉圈的話,那只能說明你的登錄賬號不能支付你的商家賬號,或者你的賬號如果登錄之后顯示添加銀行卡的提示,說明你的商家賬號和你的賬號都是中國賬號,那你只能添加多幣種卡支付,不能用銀聯支付了):
image 
image 
image 
付款就好了!
3. 接口方式我就沒有使用彈出頁面了,最簡單的方式(接口會直接在調用接口的頁面觸發支付跳轉),點擊接口支付
image 
image 
image 
image 
我就不支付了,我用的商家賬號是自己的新加坡的賬號, 按照今天的匯率 $0.01 = ¥0.068,你至少需要支付 0.07 元才能完成支付,而文章開頭也說了,商家需要付稅,也就是說你支付的 0.07 都會變成給 Paypal 的稅,商家一分錢也拿不到,也就是說,你至少支付 3.5元人民幣($0.51 = ¥3.481)商家才能得到微額的款項。
下面給出 Demo 源碼,源碼中配置的商家號是我自己的,請自行修改,為了方便大家沒有商家賬號的朋友做測試我就不刪除了,朋友們也不要真的支付測試,你的測試只會讓 Paypal 賺錢而已!
我的開發環境是 VS2015 + C# 6.0 + JS ,代碼僅供參考,請自行修改擴展學習使用!

也可以來我自己的網站坐坐:https://blog.lovelong.cn/
 
下載代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM