网站建设一般需要多少钱中国四大软件外包公司
Stripe是一家美国科技公司,成立于2010年,由爱尔兰兄弟Patrick Collison和John Collison共同创立。该公司致力于提供高效、简洁的互联网支付收款服务,为开发者或商家提供支付API接口或代码,使商家的网站、移动APP支持信用卡付款。Stripe被誉为“移动时代的PayPal”,因其简便的支付方式而受到广泛欢迎。
Stripe 总共有三种支付方式:
1、Stripe Checkout,pay links 创建支付链接,
2、payment intent,后端预下单,返回秘钥,前端确定订单
3、前端创建支付token ,后端创建Charge,返回支付结果链接
Stripe接口调用时序图
0、初始化客户端
StripeClient client = StripeClient.builder().setConnectTimeout(30 * 1000).setReadTimeout(80 * 1000).setApiKey("sk_test_51PtO7DC6XhwanSnNvGezNPc4hsL2F****").build();
1、产品
查询或创建新产品。
每次交易传入产品名称或描述,自动查询是否已经存在,如果存在则直接使用,如果不存在则新建产品。
private Product getProduct(StripeOrder stripeOrder) throws StripeException {ProductSearchParams searchParams =ProductSearchParams.builder().setQuery("active:'true' AND name:'" + stripeOrder.getSubject()+ "' AND description:'" + stripeOrder.getBody() + "'").setLimit(1L).build();StripeSearchResult<Product> result = client.products().search(searchParams);Product product;if (result != null && !result.getData().isEmpty()) {product = result.getData().stream().findFirst().get();} else {//创建产品 https://stripe.com/docs/api/products/createProductCreateParams params = ProductCreateParams.builder().setDescription(stripeOrder.getBody()).setName(stripeOrder.getSubject()).build();product = client.products().create(params);}return product;}
2、价格
根据产品ID创建对应币种的价格,指定 lookupKey = 价格+币种+产品ID,作为价格关键字,用于查询是否已经存在。如果价格存在则直接使用,如果不存在则新增新的价格。
private Price getPrice(StripeOrder stripeOrder, String productId) throws StripeException {Long unitAmount = Util.conversionCentAmount(stripeOrder.getPrice());String lookupKey = unitAmount + stripeOrder.getCurrencyCode() + productId;PriceSearchParams params =PriceSearchParams.builder().setQuery("active:'true' AND product:'" + productId+ "' AND currency:'" + stripeOrder.getCurrencyCode()+ "' AND lookup_key:'" + lookupKey + "'").build();StripeSearchResult<Price> result = client.prices().search(params);Price price;if (result != null && !result.getData().isEmpty()) {price = result.getData().stream().findFirst().get();} else {//创建价格 https://stripe.com/docs/api/prices/create
// PriceCreateParams.Recurring recurring = PriceCreateParams.Recurring.builder()
// .setInterval(PriceCreateParams.Recurring.Interval.MONTH).build();PriceCreateParams priceCreateParams = PriceCreateParams.builder().setCurrency(stripeOrder.getCurrencyCode()).setProduct(productId).setLookupKey(lookupKey).setUnitAmount(unitAmount)
// .setRecurring(recurring).build();price = client.prices().create(priceCreateParams);}return price;
}
3、创建支付
根据上次返回的价格信息,创建新的支付对象,这里指定银行卡支付,也可以指定别的支付方式;Stripe支持几十种支付方式,可以根据不同国家选择,具体可以在这里查看
下面通过 client.checkout().sessions().create() 创建支付,取得支付链接,在浏览器直接打开即可支付。
public Map<String, Object> orderInfo(PayOrder order) {StripeOrder stripeOrder = (StripeOrder) order;try {Product product = getProduct(stripeOrder);Price price = getPrice(stripeOrder, product.getId());//创建支付信息 得到urlSessionCreateParams sessionCreateParams = SessionCreateParams.builder().setMode(SessionCreateParams.Mode.PAYMENT).addPaymentMethodType(SessionCreateParams.PaymentMethodType.CARD)
// .addPaymentMethodType(SessionCreateParams.PaymentMethodType.ALIPAY).setSuccessUrl(payConfigStorage.getReturnUrl()).setCancelUrl(payConfigStorage.getCancelUrl()).setCustomer(stripeOrder.getCustomer()).setClientReferenceId(stripeOrder.getClientReferenceId()).setCustomerEmail(stripeOrder.getCustomerEmail()).addLineItem(SessionCreateParams.LineItem.builder().setQuantity(1L).setPrice(price.getId()).build()).putMetadata("outTradeNo", stripeOrder.getOutTradeNo()).build();Session session = client.checkout().sessions().create(sessionCreateParams);LOG.info("session:{}", JSON.toJSONString(session));return preOrderHandler(Collections.singletonMap("paymentLink", session.getUrl()), order);} catch (StripeException e) {throw new RuntimeException(e);}
}
4、打开支付链接,完成支付
输入Stripe平台提供的测试卡信息,完成支付
test@example.com
4242 4242 4242 4242
12/34
567
Zhang San
United States
12345
5、支付订单查询
传入回调信息中得到的chargeId,查询订单状态。在返回的json数据了包含支付平台receiptUrl
public Map<String, Object> query(AssistOrder assistOrder) {try {Charge charge = client.charges().retrieve(assistOrder.getTradeNo());// 使用Hutool的BeanUtil将User对象转换为Mapreturn BeanUtil.beanToMap(charge);} catch (Exception e) {throw new RuntimeException(e);}
}
支付凭证
6、退款
传入回调信息中得到的chargeId,提交退款请求;返回退款订单信息,包含退款订单Id(可用于查询退款订单)
public RefundResult refund(RefundOrder refundOrder) {RefundCreateParams params = RefundCreateParams.builder().setCharge(refundOrder.getTradeNo()).build();try {Refund refund = client.refunds().create(params);LOG.info("refund:{}", JSON.toJSONString(refund));StripeRefundResult refundResult = new StripeRefundResult(refund, refundOrder.getTradeNo());refundOrder.setRefundNo(refundResult.getRefundNo());return refundResult;} catch (StripeException e) {throw new RuntimeException(e);}
}
7、退款查询
传入回调信息中得到的chargeId,查询退款订单;也可以通过退款订单Id查询。
public Map<String, Object> refundquery(RefundOrder refundOrder) {RefundListParams params = RefundListParams.builder().setCharge(refundOrder.getTradeNo()).build();try {StripeCollection<Refund> result = client.refunds().list(params);if (!result.getData().isEmpty()) {Refund refund = result.getData().stream().findFirst().get();// 使用Hutool的BeanUtil将User对象转换为Mapreturn BeanUtil.beanToMap(refund);}} catch (StripeException e) {throw new RuntimeException(e);}return null;
}
8、回调通知
登录Stripe平台,在https://dashboard.stripe.com/workbench/webhooks中配置Webhook;菜单地址“开发人员-Webhook”。
Webhook回调代码示例
public class StripePayMessageHandler implements PayMessageHandler<PayMessage, StripePayService> {private final Logger LOG = LoggerFactory.getLogger(getClass());private final String endpointSecret = "whsec_HlzC2omyh4V4X3BCgMx5PScTCmwZpvAC";//webhook秘钥签名@Overridepublic PayOutMessage handle(PayMessage payMessage, Map<String, Object> context, StripePayService payService) throws PayErrorException {Map<String, Object> message = payMessage.getPayMessage();NoticeParams noticeParams = (NoticeParams) context.get("noticeParams");try {String sigHeader = noticeParams.getHeader("Stripe-Signature");Event event = Webhook.constructEvent(noticeParams.getBodyStr(), sigHeader, endpointSecret);//验签,并获取事件StripeObject eventObj = event.getDataObjectDeserializer().getObject().get();LOG.info("EventType:{}, Event:{}", event.getType(), JSON.toJSONString(eventObj));PaymentIntent intent;Charge charge;String outTradeNo;String chargeId;String receiptUrl;switch (event.getType()) {case "charge.succeeded"://支付成功//TODO 支付成功,处理业务逻辑charge = (Charge) eventObj;// 取得 chargeId ,在退款时使用chargeId = charge.getId();receiptUrl = charge.getReceiptUrl();message.put("trade_no", chargeId);LOG.info("支付成功 Charge, chargeId:{}, receiptUrl:{}", chargeId, receiptUrl);break;case "checkout.session.completed":// 通过支付链接 支付完成//TODO 支付完成,处理业务逻辑Session session = (Session) eventObj;outTradeNo = session.getMetadata().get("outTradeNo");//自定义订单号LOG.info("支付完成 Session, 订单号为:{}", outTradeNo);message.put("out_trade_no", outTradeNo);break;case "charge.refunded"://退款成功charge = (Charge) eventObj;if (charge.getStatus().equals("succeeded")) {//TODO 退款成功,处理业务逻辑chargeId = charge.getId();receiptUrl = charge.getReceiptUrl();message.put("trade_no", chargeId);LOG.info("退款成功, chargeId:{}, receiptUrl:{}", chargeId, receiptUrl);}break;case "checkout.session.expired"://过期break;case "payment_intent.created"://创建订单 这里事件就是图二选着的事件break;case "payment_intent.canceled"://取消订单break;case "payment_intent.succeeded"://支付成功intent = (PaymentIntent) eventObj;Map<String, String> metaData = intent.getMetadata();//自定义传入的参数outTradeNo = metaData.get("outTradeNo");//自定义订单号message.put("out_trade_no", outTradeNo);LOG.info("支付成功 payment_intent, 订单号为:{}", outTradeNo);//*********** 根据订单号从数据库中找到订单,并将状态置为成功 *********//*break;case "payment_intent.payment_failed"://支付失败intent = (PaymentIntent) eventObj;LOG.info("Failed: " + intent.getId());break;default:break;}} catch (Exception e) {LOG.error("stripe异步通知(webhook事件)", e);}// TODO 支付确认逻辑处理return payService.successPayOutMessage(payMessage);}
}
参考
- https://docs.stripe.com/api
- https://docs.stripe.com/js
- https://docs.stripe.com/webhooks
- https://docs.stripe.com/search#query-fields-for-products