好记星不如烂笔头,这里记录平时工作中用到的东西,不喜可以留言。
美国跨境支付stripe
测试说明,你需要办理至少一张国际信用卡,
比如visa、master、AE(American Express credit 卡)都可以,国内的银联卡不支持的。
eg: 已经隐藏卡号,部分内容,请不要测试。可以自己在国内办理。或者使用测试环境进行测试;说明,这些卡,国内信用卡都可以办理,之前如果有信用卡,开通这些行用卡非常过,基本上可以直接申请。国内各大银行都可以申请双币或者多币卡
4984 5130 xxxx xxxX 0X/23 XXX
3771 146143 xxxxX 0X/22 36XX
4895xxxx4960614X 1X/21 65X
3793 xxxx50 2100X 1X/24 31X1


- stripe支付模式和国内的支付宝、微信完全不一样,支付金额全部是后台自己控制。
- 一个订单实付支付重复也需要自己进行判断,stripe不做重复性的判断。
- stripe的手续费:stripe支付,一般最低要求4 H K , 每 笔 基 本 上 都 会 扣 除 2.5 HK, 每笔基本上都会扣除2.5 HK,每笔基本上都会扣除2.5HK的手续费,还是非常贵的。
- JAVA后台代码
package com.ourslook.mall.api.pay;package com.ourslook.mall.api.pay;import com.ourslook.mall.util.DateUtils;
import com.ourslook.mall.util.RandomUtils;
import com.ourslook.mall.util.RrException;
import com.ourslook.mall.util.XaUtils;
import com.ourslook.mall.util.annotation.IgnoreAuthToken;
import com.ourslook.mall.util.distributedlock.DistributedLockUtil;
import com.ourslook.mall.util.distributedlock.IDistributedLock;
import com.ourslook.mall.util.pay.stripe.StripePayUtil;
import com.ourslook.mall.util.result.XaResult;
import com.ourslook.mall.util.validator.AbstractAssert;
import com.stripe.Stripe;
import com.stripe.exception.InvalidRequestException;
import com.stripe.exception.StripeException;
import com.stripe.model.BalanceTransaction;
import com.stripe.model.Charge;
import com.stripe.model.Customer;
import com.stripe.model.Refund;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;/*** @author dazr* @version V1.0* <p>* 美国跨境支付stripe 支付相关接口* @date 2019年6月10日 下午1:00:00* <p>* 卡支付:https://stripe.com/docs/sources/cards* api首页:https://stripe.com/docs/api* <p>* mvn jar 搜索:com.stripe stripe-java 即可* <p>* [没有体验过stripe同学,可以使用modao体验](https://free.modao.cc/me/settings)* <p>* https://demo.mall.com.cn/* https://www.qcurepay.co/* https://chainmate.io/* <p>* 自定义stripe checkout 比如自定义语言 Customizing Checkout https://stripe.com/docs/payments/checkout/customization#null* 自定义 elements 语言locate zh https://stripe.com/docs/stripe-js/reference#stripe-elements* <p>* 关于: 小程序使用stripe的说明,小程序没有dom元素。就无法直接生成stripe支付使用src原id: 见 小程序stripe https://segmentfault.com/q/1010000015548618*/
@Api(value = "pay_stripe", description = "美国跨境支付strpe", position = 10)
@Controller
@CrossOrigin
@RequestMapping("/api/stripe")
public class ApiPayStripeController {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate StringRedisTemplate redisTemplate;/*** 步骤一:客户端初始化代码 创建Source对象* 文档路径 https://stripe.com/docs/sources/cards#create-source* 代码片段 https://stripe.com/docs/stripe-js/elements/quickstart* <p>* <p>* 步骤二 Charge the Source 【为Source支付】* 2.1:Attaching the Source to a Customer 根据客户端source获取客户的详细信息Customer* 文档路径/代码片段:https://stripe.com/docs/sources/cards#charge-request* 2.2:Making a charge request to finalize the payment 提出收费要求以完成付款* 文档路径/代码片段:https://stripe.com/docs/sources/cards#making-a-charge-request-to-finalize-the-payment* <p>* <p>* <p>* 步骤三 Step 3: Confirm that the charge has succeeded 可以通过自定义回调webhook事件确定是否支付成功* https://stripe.com/docs/sources/cards#charge-confirmation* EVENT:charge.succeeded* EVENT:charge.failed* <p>* <p>* http://127.0.0.1:8988/mall/api/stripe/creditCardCharges?stripeSourceId=src_1FtqeOJ8lGCmQwVAVvwlcIlx&email=ab601026460@163.com&orderNo=QC111111* <p>* 说明,stripe的手续费很高,2元+3%左右的一个手续费,并且退款了,这个手续费也收着的;*/@SuppressWarnings("all")@ApiOperation(value = "信用卡支付", notes = "Charge the Source【为Source支付】 ,具体分为两步【1: 根据客户端创建的source获取顾客信息 Customer 2:支付成功】;针对:Visa、Mastercard和American Express 支付")@ResponseBody@IgnoreAuthToken@RequestMapping(value = "creditCardCharges", method = RequestMethod.GET)public XaResult<Charge> creditCardPay(@ApiParam(value = "客户端输入了信用卡账号之后创建的source对象/token对象,详见 文档 【Step 1: Create a Source object】,字段名:stripeSourceId;这里是sourceId或者token都可以; token:tok_KPte7942xySKBKyrBu11yEpf", defaultValue = "src_18eYalAHEMiOZZp1l9ZTjSU0") @RequestParam(value = "stripeSourceId", required = true) String stripeSourceId,@ApiParam(value = "email,字段名:email,请填写自己的email", defaultValue = "ab601026460@163.com") @RequestParam(value = "email", required = false) String email,@ApiParam(value = "订单号,字段名:orderNo", defaultValue = "QC1234567890") @RequestParam(value = "orderNo", required = false) String orderNo,HttpServletRequest request) throws RrException {orderNo = !XaUtils.isValid(orderNo) ? RandomUtils.getRandomOrderNo() + "__" + System.currentTimeMillis() : orderNo;AbstractAssert.isOk(XaUtils.isValid(orderNo), "订单号不能为空, orderNo = " + orderNo);XaResult<Charge> xr = new XaResult();// 防止出现重复支付,这里使用分布式锁// 分布式锁lock-1: 获取锁,超时15S; 锁过期使用2分钟; 锁解决高并发问题IDistributedLock distributedLock = DistributedLockUtil.getDistributedLock("order:" + orderNo, 2 * 60 * 1000, 15 * 1000);if (!distributedLock.acquire()) {//超时,没有获取到分布式锁,这里直接拒绝xr.error("正在支付,请稍等..(lock获取失败)");return xr;}String redisKey = DistributedLockUtil.assembleKey("order:" + orderNo + ":data");boolean isPaySucess = false;try {// 分布式锁lock-2: 前置条件判断// 如果已经获取到锁,这里必须进行前置条件判断。解决重复执行// (eg: 支付、下单、抽奖、等业务逻辑)【可能用户重复点击,但是执行很快,导致多次执行】// 这里需要重数据库重新取值,才可以。if (XaUtils.isNotEmpty(redisTemplate.opsForValue().get(redisKey))) {xr.error("已经支付,请勿重复支付..");return xr;}// 如果,没有支付过,这里设置一个标志redisTemplate.opsForValue().set(redisKey, DateUtils.getCurrentDateAsFileName(), 15, TimeUnit.DAYS);// Set your secret key: remember to change this to your live secret key in production// See your keys here: https://dashboard.stripe.com/account/apikeysStripe.apiKey = StripePayUtil.API_KEY;//======StripeStep1: 根据客户端source获取到customer对象Map<String, Object> customerParams = new HashMap<String, Object>();customerParams.put("email", XaUtils.getNutNullStr(email, "我是默认的邮箱dazer@dazer.com"));//选填:emailcustomerParams.put("name", "李易峰(顾客姓名)");//选填: name 如,顾客的姓名customerParams.put("description", "我是商品描述");//选填: description 如,商品的名称customerParams.put("source", stripeSourceId);//soureZZZId: 类似 src_18eYalAHEMiOZZp1l9ZTjSU0 ,必须是客户端输入信用卡账号之后客户端获取的原对象的id//下面三个参数都是可选参数这里可以添加可选自定义参数,如自定义:order_no//描述,可以选; 参数名字,可以自己随便起Map<String, String> metadata = new HashMap<>();metadata.put("order_no", "88888888888");metadata.put("email", "liyifeng@163.com");metadata.put("tag", "活动付款");customerParams.put("metadata", metadata);Customer customer = null;try {// Customer 对象api https://stripe.com/docs/api/customers/objectcustomer = Customer.create(customerParams);} catch (StripeException e) {e.printStackTrace();if (e instanceof InvalidRequestException && e.getLocalizedMessage().contains("The source you provided has already been attached to a customer")) {throw new RrException("你已经支付过一次了,请不要重复点击;", e);}throw new RrException("stripe获取对象失败,详看日志:" + e.getMessage(), e);}//======StripeStep2: 根据customer对象进行支付Map<String, Object> chargeParams = new HashMap<String, Object>();// 港币:Amount must be at least 400 cents 美分 4港币; 港币目前至少是400// 人民币:Amount must convert to at least 400 cents. ¥0.01 converts to approximately $0.01.; 人民币:355分// 美元:最少 58 美分 US$// 美元:最少 400 港分 HK$// 最终都是依港币进行结算的chargeParams.put("amount", 1001);//必须是整数chargeParams.put("currency", "HKD");//USD 美元、CNY 人民币、HKD 港币chargeParams.put("customer", customer.getId());//类似: cus_AFGbOSiITuJVDschargeParams.put("source", stripeSourceId);//类似:src_18eYalAHEMiOZZp1l9ZTjSU0chargeParams.put("metadata", metadata);//描述,可以选// Charge 对象api https://stripe.com/docs/api/charges/objectCharge charge = null;try {charge = Charge.create(chargeParams);} catch (StripeException e) {e.printStackTrace();if (StripePayUtil.CODE_AMOUNT_TOO_SMALL.equalsIgnoreCase(e.getCode())) {throw new RrException("stripe支付金额太小,至少400美分,注意实时汇率;详:" + e.getMessage(), e);} else {throw new RrException("stripe调用Charge失败,详看日志:" + e.getMessage(), e);}}if (logger.isInfoEnabled()) {logger.info("charge对象获取成功,但不一定表示支付成功,虽然大部分情况是支付成功的...");}xr.setObject(charge);// 这里需要把 chargeid [支付id 方便退款] 和 balanceTransactionId[交易id方便查账单、手续费等] 保存起来, 方便退款。eg: ch_1FtlhDJ8lGCmQwVAyZ5Jjx1Z//https://stripe.com/docs/api/charges/object#charge_object-statusif ("succeeded".equalsIgnoreCase(charge.getStatus())) {//succeeded 支付成功//======StripeStep3 获取交易的手续费//这里获取本笔的校验信息 https://stripe.com/docs/api/balance_transactions/objectString balanceTransactionId = charge.getBalanceTransaction();BalanceTransaction balanceTransaction = null;try {balanceTransaction = BalanceTransaction.retrieve(balanceTransactionId);} catch (StripeException e) {e.printStackTrace();}if (balanceTransaction != null) {//交易总金额,单位美分Long amount = balanceTransaction.getAmount();//交易收取的手续费,单位美分Long fee = balanceTransaction.getFee();// 货币单位String currency = balanceTransaction.getCurrency().toUpperCase();logger.info("订单:{},交易总金额amount:{}{}, 其中手续费fee:{}{}", orderNo, amount, currency, fee, currency);}try {//这里处理业务// xxxService.xxxx//xr.setMsg("succeeded 支付成功");isPaySucess = true;} catch (Exception e) {e.printStackTrace();//======StripeStep4//万一业务处理失败,抛出异常。这里需要进行退款,否则业务就出现问题了if (charge != null && XaUtils.isNotEmpty(charge.getId())) {// 万一,付款成功之后,执行失败了,这里进行退款Map<String, Object> params = new HashMap<>();params.put("charge", charge.getId());//eg: ch_IGZpZZVn6H47dNUC2VOU//params.put("amount", 1000);//要退还部分费用,请提供一个amount参数,以整数美分(或收费货币的最小货币单位)Refund refund = null;try {refund = Refund.create(params);} catch (StripeException ee) {e.printStackTrace();}}throw new RrException("支付失败,请联系管理员!");}} else if ("pending".equalsIgnoreCase(charge.getStatus())) {//pending 支付结果要继续进行等待xr.setMsg("pending 支付结果要继续进行等待");} else {//failed 支付失败xr.setMsg("failed 支付失败");}} finally {// 分布式锁lock-3-1: 释放锁if (distributedLock != null) {distributedLock.release();}// 分布式锁lock-3-1: 如果执行失败,这里要删除keyif (!isPaySucess) {redisTemplate.delete(redisKey);}}return xr;}/*** 退款* 代码片段/文档:https://stripe.com/docs/refunds#refunds-charges* <p>* http://127.0.0.1:8001/qcure/api/stripe/refunds?chargeId=ch_1FRUuTEEsRhJ9o6lfcg1mmnq*/@ApiIgnore@SuppressWarnings("all")@ApiOperation(value = "refunds退款", notes = "refunds退款")@ResponseBody@IgnoreAuthToken@RequestMapping(value = "refunds", method = RequestMethod.GET)public XaResult<Refund> refunds(@ApiParam(value = "charge对象id,字段名:chargeId,信用卡支付成功获取到的charge对象", defaultValue = "ch_1Elrq4EEsRhJ9o6ldDCqaS36") @RequestParam(value = "chargeId", required = false) String chargeId,HttpServletRequest request) throws Exception {// Set your secret key: remember to change this to your live secret key in production// See your keys here: https://dashboard.stripe.com/account/apikeysStripe.apiKey = StripePayUtil.API_KEY;XaResult<Refund> xr = new XaResult<>();Map<String, Object> params = new HashMap<>();params.put("charge", chargeId);//eg: ch_IGZpZZVn6H47dNUC2VOU//params.put("amount", 1000);//要退还部分费用,请提供一个amount参数,以整数美分(或收费货币的最小货币单位)Refund refund = null;try {refund = Refund.create(params);xr.setObject(refund);if ("succeeded".equalsIgnoreCase(refund.getStatus())) {xr.setMsg("退款成功");} else {xr.setMsg("退款失败");}} catch (StripeException e) {if (StripePayUtil.CODE_CHARGE_ALREADY_REFUNDED.equalsIgnoreCase(e.getCode())) {xr.setMsg("退款成功, 您已经退款过了,请不要重复退款!");} else {throw new RrException("退款申请失败:" + e.getMessage(), e);}e.printStackTrace();}return xr;}
}
/*** @author xy*/
public class StripePayUtil {/*** apikey查看地方* https://dashboard.stripe.com/apikeys* 正式环境** pk_live_4L75w76q0pJ9f9aCcuy0JuNw003XdosVg1 正式* pk_test_TYooMQauvdEDq54NiTphI7jx 正式** SK:sk_live_dj7tGb1BvwyJqN0TdfbMmpmk00EYXbo6jV 密钥; 后台使用 【sk_live_】* PK: pk_live_qFarErZ5k3Cc5WHmbT5AdEY000n6CKuADA 可以发布密钥; 前端使用: 【pk_live_】*/public static final String API_KEY = "sk_live_dj7tGb1BvwyJqN0TdfbMmpmk00EYXbbo6jV";/**测试环境*//* public static final String API_KEY = "sk_test_1M3fvX5yaujIJJgTHaN0dnSJ00fKueenTu1";*/// public static final String API_KEY = "sk_test_4eC39HqLyjWDarjtT1zdp7dcn";/**支付金额太少,至少400美分*/public static final String CODE_AMOUNT_TOO_SMALL = "amount_too_small";/**请勿重复退款*/public static final String CODE_CHARGE_ALREADY_REFUNDED = "charge_already_refunded";/*** 步骤一:客户端初始化代码 创建Source对象* 文档路径 https://stripe.com/docs/sources/cards#create-source* 代码片段 https://stripe.com/docs/stripe-js/elements/quickstart* <p>* <p>* 步骤二 Charge the Source 【为Source支付】* 2.1:Attaching the Source to a Customer 根据客户端source获取客户的详细信息Customer* 文档路径/代码片段:https://stripe.com/docs/sources/cards#charge-request* 2.2:Making a charge request to finalize the payment 提出收费要求以完成付款* 文档路径/代码片段:https://stripe.com/docs/sources/cards#making-a-charge-request-to-finalize-the-payment* <p>* <p>* <p>* 步骤三 Step 3: Confirm that the charge has succeeded 可以通过自定义回调webhook事件确定是否支付成功* https://stripe.com/docs/sources/cards#charge-confirmation* EVENT:charge.succeeded* EVENT:charge.failed* <p>* <p>* http://127.0.0.1:8001/qcure/api/stripe/creditCardPay*/
}
- stripe信用卡付款html前端代码
<html>
<!--文档路径 https://stripe.com/docs/sources/cards#create-source-->
<!--代码片段 https://stripe.com/docs/stripe-js/elements/quickstart -->
<!-- 如果需要自定义样式:https://stripe.com/docs/payments/checkout https://stripe.dev/elements-examples/ -->
<!--Customizing Checkout 自定义设置checkout比如设置 语言 https://stripe.com/docs/payments/checkout/customization#null -->
<head><script src="https://js.stripe.com/v3/"></script><!--css样式--><style type="text/css">/*** The CSS shown here will not be introduced in the Quickstart guide, but shows* how you can use CSS to style your Element's container.*/.StripeElement {box-sizing: border-box;height: 40px;padding: 10px 12px;border: 1px solid transparent;border-radius: 4px;background-color: white;box-shadow: 0 1px 3px 0 #e6ebf1;-webkit-transition: box-shadow 150ms ease;transition: box-shadow 150ms ease;}.StripeElement--focus {box-shadow: 0 1px 3px 0 #cfd7df;}.StripeElement--invalid {border-color: #fa755a;}.StripeElement--webkit-autofill {background-color: #fefde5 !important;}</style><script type="text/javascript" language="JavaScript">window.onload=function (){// Create a Stripe client.var stripe = Stripe('pk_live_4L75w76q0pJ9f9aCcuy0JuNw003XdosVg1');// Create an instance of Elements.// Create an instance of Elements.// 自定义 elements 语言 https://stripe.com/docs/stripe-js/reference#stripe-elementsvar elements = stripe.elements({locale:'zh'});// Custom styling can be passed to options when creating an Element.// (Note that this demo uses a wider set of styles than the guide below.)var style = {base: {color: '#32325d',fontFamily: '"Helvetica Neue", Helvetica, sans-serif',fontSmoothing: 'antialiased',fontSize: '16px','::placeholder': {color: '#aab7c4'}},invalid: {color: '#fa755a',iconColor: '#fa755a'}};// Create an instance of the card Element.var card = elements.create('card', {style: style});// Add an instance of the card Element into the `card-element` <div>.card.mount('#card-element');// Handle real-time validation errors from the card Element.card.addEventListener('change', function(event) {var displayError = document.getElementById('card-errors');if (event.error) {displayError.textContent = event.error.message;} else {displayError.textContent = '';}});// Handle form submission.var form = document.getElementById('payment-form');form.addEventListener('submit', function(event) {event.preventDefault();stripe.createSource(card).then(function(result) {if (result.error) {// Inform the user if there was an error.var errorElement = document.getElementById('card-errors');errorElement.textContent = result.error.message;} else {// Send the token to your server.stripeTokenHandler(result.source);}});});};// Submit the form with the token ID.function stripeTokenHandler(source) {// Insert the token ID into the form so it gets submitted to the servervar form = document.getElementById('payment-form');var hiddenInput = document.createElement('input');hiddenInput.setAttribute('type', 'hidden');hiddenInput.setAttribute('name', 'stripeSourceId');hiddenInput.setAttribute('value', source.id);form.appendChild(hiddenInput);// Submit the formform.submit();}</script>
</head>
<body><form action="http://127.0.0.1:8001/qcure/api/stripe/creditCardCharges" method="GET" id="payment-form"><div class="form-row"><label for="card-element">Credit or debit card</label><div id="card-element"><!-- A Stripe Element will be inserted here. --></div><!-- Used to display form errors. --><div id="card-errors" role="alert"></div></div><button>Submit Payment</button>
</form>
</body>
</html>
stripe 信用卡支付demo - csdn下载地址


















