一、 java对接PayPal支付(v2). 讲了PayPal v2:checkout-sdk 的对接过程
二、 java对接PayPal支付 (添加物流跟踪信息). 讲了PayPal添加物流信息 的对接过程
今天我们讲讲如何实现自动续费的功能。
PayPal自动续费功能在v1版本里面有,如果想实现此功能,必须引入v1 rest-api-sdk
言归正传,正式开始
<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>rest-api-sdk</artifactId>
<version>LATEST</version>
</dependency>
package com.ratta.pay.info.paypal;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.paypal.api.payments.ChargeModels;
import com.paypal.api.payments.Currency;
import com.paypal.api.payments.MerchantPreferences;
import com.paypal.api.payments.Patch;
import com.paypal.api.payments.PaymentDefinition;
import com.paypal.api.payments.Plan;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.PayPalRESTException;
public class MakeRenewalPlan{
private String clientId = "AdTJ2en6Av42r1xoziJj6bJK-X4tRGDHACZId0OPXfGyXs8OyoYEmlm8bHjzrgd3UislDQR0iBP7x-wM";
private String clientSecret = "EC9DSrfAfVfo-c3K-1dILXA5iijnHtaunKwv2JzECSz9jcdy3t78rDFeAEgaixnnIYYlgQcipbZQWoCa";
private String mode = "sandbox";
protected Plan instance = null;
/**
* 创建续费计划
*
* https://developer.paypal.com/webapps/developer/docs/api/#create-a-plan
*
* @return 新创建的计划
* @throws PayPalRESTException
*/
public Plan create(APIContext context) throws PayPalRESTException, IOException {
// 构建计划对象
Plan plan = new Plan();
plan.setName("Supernote会员");
plan.setDescription("Supernote会员,按月自动续费");
//计费方案的类型。 允许的值:FIXED,INFINITE
plan.setType("FIXED");
//付款参数
PaymentDefinition paymentDefinition = new PaymentDefinition();
paymentDefinition.setName("定期付款");
//付款定义的类型。 允许的值:“ TRIAL”,“ REGULAR”)。
paymentDefinition.setType("REGULAR");
//提供的付款定义的频率。 允许的值:“WEEK”,“DAY”,“YEAR”,“MONTH”。
paymentDefinition.setFrequency("MONTH");
//多久(setFrequency)向用户收取一次费用
paymentDefinition.setFrequencyInterval("1");
//收款的周期(次数),无期限使用“0”
paymentDefinition.setCycles("12");
//金额
Currency currency = new Currency();
currency.setCurrency("USD");
currency.setValue("20");
paymentDefinition.setAmount(currency);
//收费模式
//ChargeModels chargeModels = new com.paypal.api.payments.ChargeModels();
//收费模式的类型。 允许的值:`SHIPPING`运费,`TAX`税费。
//chargeModels.setType("SHIPPING");
//chargeModels.setAmount(currency);
//List chargeModelsList = new ArrayList();
//chargeModelsList.add(chargeModels);
//paymentDefinition.setChargeModels(chargeModelsList);
//付款定义
List<PaymentDefinition> paymentDefinitionList = new ArrayList<PaymentDefinition>();
paymentDefinitionList.add(paymentDefinition);
plan.setPaymentDefinitions(paymentDefinitionList);
//商家设置
MerchantPreferences merchantPreferences = new MerchantPreferences();
merchantPreferences.setSetupFee(currency);
merchantPreferences.setCancelUrl("https://www.example.com");
merchantPreferences.setReturnUrl("https://www.example.com");
//允许的失败尝试总数。默认值为0,表示无数次失败的尝试
merchantPreferences.setMaxFailAttempts("0");
//允许在下一个周期中为未结协议金额自动计费。 允许的值:“YES”,“NO”。 默认为“NO”。
merchantPreferences.setAutoBillAmount("YES");
//如果在首次付款期间发生故障,应采取的措施。 允许的值:"CONTINUE","CANCEL"。 默认为"CONTINUE"。
merchantPreferences.setInitialFailAmountAction("CONTINUE");
plan.setMerchantPreferences(merchantPreferences);
this.instance = plan.create(context);
System.out.println("planId:" + this.instance.getId());
return this.instance;
}
/**
* 更新续费计划(激活)
*
* https://developer.paypal.com/webapps/developer/docs/api/#update-a-plan
*
* @return 更新后的计划
* @throws PayPalRESTException
*/
public Plan update(APIContext context) throws PayPalRESTException, IOException {
List<Patch> patchRequestList = new ArrayList<Patch>();
Map<String, String> value = new HashMap<String, String>();
value.put("state", "ACTIVE");
Patch patch = new Patch();
patch.setPath("/");
patch.setValue(value);
patch.setOp("replace");
patchRequestList.add(patch);
this.instance.update(context, patchRequestList);
return this.instance;
}
/**
* 查询续费计划
*
* https://developer.paypal.com/webapps/developer/docs/api/#retrieve-a-plan
*
* @return 查询到的续费计划
* @throws PayPalRESTException
*/
public Plan retrieve(APIContext context) throws PayPalRESTException {
return Plan.get(context, this.instance.getId());
}
public static void main(String[] args) {
try {
MakeRenewalPlan subscriptionSample = new MakeRenewalPlan();
APIContext context = new APIContext(clientId, clientSecret, mode);
subscriptionSample.create(context);
System.out.println("create response:\n" + Plan.getLastResponse());
subscriptionSample.update(context);
System.out.println("plan updated");
// subscriptionSample.retrieve(context);
// System.out.println("retrieve response:\n" + Plan.getLastResponse());
} catch (JsonSyntaxException e) {
e.printStackTrace();
} catch (JsonIOException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (PayPalRESTException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
*运行上述main方法后,得到如下报文:
{"id":“P-3D664421HH6274501ORVH46A”,“state”:“CREATED”,“name”:“Supernote会员”,“description”:“Supernote会员,按月自动续费”,“type”:“FIXED”,“payment_definitions”:[{“id”:“PD-1BW495186X851642WORVH46A”,“name”:“定期付款”,“type”:“REGULAR”,“frequency”:“Month”,“amount”:{“currency”:“USD”,“value”:“20”},“cycles”:“12”,“charge_models”:[{“id”:“CHM-1A988195SK289121WORVH46A”,“type”:“SHIPPING”,“amount”:{“currency”:“USD”,“value”:“20”}}],“frequency_interval”:“1”}],“merchant_preferences”:{“setup_fee”:{“currency”:“USD”,“value”:“20”},“max_fail_attempts”:“0”,“return_url”:“https://www.example.com”,“cancel_url”:“https://www.example.com”,“auto_bill_amount”:“YES”,“initial_fail_amount_action”:“CONTINUE”},“create_time”:“2020-07-22T02:46:43.064Z”,“update_time”:“2020-07-22T02:46:43.064Z”,“links”:[{“href”:“https://api.sandbox.paypal.com/v1/payments/billing-plans/P-3D664421HH6274501ORVH46A”,“rel”:“self”,“method”:“GET”}]}
我们要的参数就是"id":"P-3D664421HH6274501ORVH46A"
package com.ratta.pay.info.paypal;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.PayPalRESTException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import com.paypal.api.payments.Agreement;
import com.paypal.api.payments.Links;
import com.paypal.api.payments.Payer;
import com.paypal.api.payments.Plan;
import com.paypal.api.payments.ShippingAddress;
public class CreateRenewalPlanOrder {
private String clientId = "AdTJ2en6Av42r1xoziJj6bJK-X4tRGDHACZId0OPXfGyXs8OyoYEmlm8bHjzrgd3UislDQR0iBP7x-wM";
private String clientSecret = "EC9DSrfAfVfo-c3K-1dILXA5iijnHtaunKwv2JzECSz9jcdy3t78rDFeAEgaixnnIYYlgQcipbZQWoCa";
private String mode = "sandbox";
public String create(String planId, String date) {
APIContext context = new APIContext(clientId, clientSecret, mode);
// 创建新的协议
Agreement agreement = new Agreement();
agreement.setName("Supernote会员");
agreement.setDescription("黄金会员,多重优惠特权");
agreement.setStartDate(date);
// 设置续费协议
Plan plan = new Plan();
plan.setId(planId);
agreement.setPlan(plan);
// 添加付款方式
Payer payer = new Payer();
payer.setPaymentMethod("paypal");
agreement.setPayer(payer);
// 添加收货地址
ShippingAddress shipping = new ShippingAddress();
shipping.setLine1("集心路168号");
shipping.setCity("shanghai");
shipping.setState("上海市");
shipping.setPostalCode("200000");
shipping.setCountryCode("CN");
agreement.setShippingAddress(shipping);
// 创建协议
try {
agreement = agreement.create(context);
for (Links links : agreement.getLinks()) {
if ("approval_url".equals(links.getRel())) {
URL url = new URL(links.getHref());
System.out.println("url = " + url);
break;
}
}
} catch (PayPalRESTException e) {
System.err.println(e.getDetails());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
Calendar cal=Calendar.getInstance();
cal.add(Calendar.DATE,1);
Date time=cal.getTime();
String dateString = sf.format(time);
//UTC时间,第二天早晨9点收取第一次费用
dateString = dateString + " 09:00:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;
try {
date = sdf.parse(dateString);
} catch (ParseException e) {
e.printStackTrace();
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
String timestamp = df.format(date);
new CreateRenewalPlanOrder().create("P-9DD04346B7017164CORRDVOA", timestamp);
}
}
得到跳转paypal收银台的url后,进行支付
支付成功后重定向到ReturnUrl地址
用户通过CreateRenewalPlanOrder生成 approveUrl 跳转paypal支付成功后,只是授权,并没有将用户的钱打入我们的paypal账户,我们需要通过 CapturesRenewalPlanOrder接口,将钱打入我的PayPal账户
package com.ratta.pay.info.paypal;
import com.paypal.api.payments.Agreement;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.PayPalRESTException;
public class CapturesRenewalPlanOrder {
private String clientId = "AdTJ2en6Av42r1xoziJj6bJK-X4tRGDHACZId0OPXfGyXs8OyoYEmlm8bHjzrgd3UislDQR0iBP7x-wM";
private String clientSecret = "EC9DSrfAfVfo-c3K-1dILXA5iijnHtaunKwv2JzECSz9jcdy3t78rDFeAEgaixnnIYYlgQcipbZQWoCa";
private String mode = "sandbox";
@SuppressWarnings("static-access")
public String captureOrder(String token) {
APIContext context = new APIContext(clientId, clientSecret, mode);
//用户支付成功后,重定向返回的页面携带的token
Agreement agreement = new Agreement();
agreement.setToken(token);
try {
Agreement activeAgreement = agreement.execute(context, agreement.getToken());
System.out.println("Agreement created with ID " + activeAgreement.getId());
} catch (PayPalRESTException e) {
System.err.println(e.getDetails());
}
return null;
}
public static void main(String[] args) {
new CapturesRenewalPlanOrder().captureOrder("EC-46H30638BD0709616");
}
}
查看PayPal商家账户
商家账户该笔订单详细信息
查看PayPal用户账户
关于更多续费计划的接口功能请参考: paypal文档(Billing Agreements API).