Partner apps: Merchant
Upon successful initialization of the SamsungPay class, the merchant app should create a transaction request with payment information. Samsung Pay offers two types of online payment sheet―a normal and custom.
The normal payment sheet has fixed display items ― “Items”, “Tax”, and “Shipping”. If these items are not applicable for your merchant needs, you should use the custom payment sheet. The custom payment sheet offers more dynamic controls for tailoring the UI look and feel with additional customer order and payment data.
Optionally, the following fields can be added to the payment information:
public class PaymentInfo implements Parcelable { private String version; private Amount amount; private Address shippingAddress; private Address billingAddress; private String merchantId; private String merchantName; private String orderNumber; private PaymentProtocol paymentProtocol; private AddressInPaymentSheet addressInPaymentSheet = AddressInPaymentSheet.DO_NOT_SHOW; private List<Brand> allowedCardBrand; private boolean isCardHolderNameRequired = false; private boolean isRecurring = false; private String merchantCountryCode; private Bundle extraPaymentInfo; }
The merchant app should send PaymentInfo to Samsung Pay via the applicable Samsung Pay SDK API method for the operation being invoked. Upon successful user authentication, Samsung Pay returns the "Payment Info" structure and the result string. The result string is forwarded to the PG for transaction completion and will vary based on the requirements of the PG used.
The code example below illustrates how to populate payment information in each field of the PaymentInfo class.
private PaymentInfo makeTransactionDetails() { ArrayList<PaymentManager.Brand> brandList = new ArrayList<>(); // If the supported brand is not specified, all card brands in Samsung Pay are listed // in the Payment Sheet. brandList.add(PaymentManager.Brand.MASTERCARD); brandList.add(PaymentManager.Brand.VISA); brandList.add(PaymentManager.Brand.AMERICANEXPRESS); PaymentInfo.Address shippingAddress = new PaymentInfo.Address.Builder() .setAddressee("name") .setAddressLine1("addLine1") .setAddressLine2("addLine2") .setCity("city") .setState("state") .setCountryCode("United States") .setPostalCode("zip") .build(); PaymentInfo.Amount amount = new PaymentInfo.Amount.Builder() .setCurrencyCode("USD") .setItemTotalPrice("1000") .setShippingPrice("10") .setTax("50") .setTotalPrice("1060") .build(); PaymentInfo.Builder paymentInfoBuilder = new PaymentInfo.Builder(); PaymentInfo paymentInfo = PaymentInfo.Builder() .setMerchantId("123456") .setMerchantName("Sample Merchant") .setOrderNumber("AMZ007MAR") .setPaymentProtocol(PaymentInfo.PaymentProtocol.PROTOCOL_3DS) // Merchant requires billing address from Samsung Pay and // sends the shipping address to Samsung Pay. // Option shows both billing and shipping address on the payment sheet. .setAddressInPaymentSheet(PaymentInfo.AddressInPaymentSheet. NEED_BILLING_SEND_SHIPPING) .setShippingAddress(shippingAddress) .setAllowedCardBrands(brandList) .setCardHolderNameEnabled(true) .setRecurringEnabled(false) .setAmount(amount) .build(); return paymentInfo; }
The result is delivered to TransactionInfoListener, which provides one of the following events:
private void startInAppPay() { // PaymentManager.startInAppPay method to show normal payment sheet try { Bundle bundle = new Bundle(); bundle.putString(SamsungPay.PARTNER_SERVICE_TYPE, SamsungPay.ServiceType.INAPP_PAYMENT.toString()); PartnerInfo partnerInfo = new PartnerInfo(serviceId, bundle); paymentManager = new PaymentManager(context, partnerInfo); /* * PaymentManager.startInAppPay is a method to request online (In-App) payment * with Samsung Pay. * Merchant app can use this method to make In-App purchase using Samsung Pay from their * application with normal payment sheet. */ paymentManager.startInAppPay(makeTransactionDetails(), transactionListener); } catch (NullPointerException e) { Toast.makeText(context, "All mandatory fields cannot be null.", Toast.LENGTH_LONG).show(); e.printStackTrace(); } catch (IllegalStateException e) { Toast.makeText(context, "IllegalStateException", Toast.LENGTH_LONG).show(); e.printStackTrace(); } catch (NumberFormatException e) { Toast.makeText(context, "Amount values is not valid", Toast.LENGTH_LONG).show(); e.printStackTrace(); } catch (IllegalArgumentException e) { Toast.makeText(context, "PaymentInfo values are not valid or all mandatory fields are not set.", Toast.LENGTH_LONG).show(); e.printStackTrace(); } } /* * TransactionInfoListener is for listening callback events of online (In-App) * payment. This is invoked when card or address is changed by the user on the * payment sheet, and also with the success or failure of online (In-App) payment. */ private PaymentManager.TransactionInfoListener transactionListener = new PaymentManager.TransactionInfoListener() { // This callback is received when the user modifies or selects a new address // on the payment sheet. @Override public void onAddressUpdated(PaymentInfo paymentInfo) { try { // Do address verification by merchant app /* setAddressInPaymentSheet(PaymentInfo.AddressInPaymentSheet. * NEED_BILLING_SEND_SHIPPING) * If you set NEED_BILLING_SEND_SHIPPING or NEED_BILLING_SPAY with * like upper codes, * you can get Billing Address with getBillingAddress(). * If you set NEED_BILLING_AND_SHIPPING or NEED_SHIPPING_SPAY, * you can get Shipping Address with getShippingAddress(). */ PaymentInfo.Address billing_address = paymentInfo.getBillingAddress(); int billing_errorCode = validateBillingAddress(billing_address); // Call updateAmount() or updateAmountFailed() method. This is mandatory. if (billing_errorCode != PaymentManager.ERROR_NONE) paymentManager.updateAmountFailed(billing_errorCode); else { PaymentInfo.Amount amount = new PaymentInfo.Amount.Builder() .setCurrencyCode("USD") .setItemTotalPrice("1000.00") .setShippingPrice("10.00") .setTax("50.00") .setTotalPrice("1060.00") .build(); paymentManager.updateAmount(amount); } } catch (IllegalStateException | NullPointerException e){ e.printStackTrace(); } } //This callback is received when the user changes card on the payment sheet in Samsung Pay @Override public void onCardInfoUpdated(CardInfo selectedCardInfo) { /* * Called when the user changes card in Samsung Pay. * Newly selected cardInfo is passed and merchant app can update transaction * amount based on new card (if needed). */ try { PaymentInfo.Amount amount = new PaymentInfo.Amount.Builder() .setCurrencyCode("USD") .setItemTotalPrice("1000.00") .setShippingPrice("10.00") .setTax("50.00") .setTotalPrice("1060.00") .build(); // Call updateAmount() method. This is mandatory. paymentManager.updateAmount(amount); } catch (IllegalStateException | NullPointerException e){ e.printStackTrace(); } } /* * This callback is received when the online (In-App) payment transaction is approved by * user and able to successfully generate In-App payload. * The payload could be an encrypted cryptogram (direct In-App payment) * or Payment Gateway's token reference ID (indirect In-App payment). */ @Override public void onSuccess(PaymentInfo response, String paymentCredential, Bundle extraPaymentData) { Toast.makeText(context, "Transaction : onSuccess", Toast.LENGTH_LONG).show(); // You can use PaymentInfo, paymentCredential and extraPaymentData. } // This callback is received when the online payment transaction has failed. @Override public void onFailure(int errorCode, Bundle errorData) { Toast.makeText(context, "Transaction : onFailure : "+ errorCode, Toast.LENGTH_LONG).show(); } };
// This callback is received when the user modifies or selects a new address on the payment sheet. @Override public void onAddressUpdated(PaymentInfo paymentInfo) { try { // Do address verification by merchant app /* setAddressInPaymentSheet(PaymentInfo.AddressInPaymentSheet.NEED_BILLING_SEND_SHIPPING) * If you set NEED_BILLING_SEND_SHIPPING or NEED_BILLING_SPAY with like upper codes, * you can get Billing Address with getBillingAddress(). * If you set NEED_BILLING_AND_SHIPPING or NEED_SHIPPING_SPAY, * you can get Shipping Address with getShippingAddress(). */ PaymentInfo.Address billing_address = paymentInfo.getBillingAddress(); int billing_errorCode = validateBillingAddress(billing_address); if (billing_errorCode != PaymentManager.ERROR_NONE) paymentManager.updateAmountFailed(billing_errorCode); else { PaymentInfo.Amount amount = new PaymentInfo.Amount.Builder() .setCurrencyCode("USD") .setItemTotalPrice("1000.00") .setShippingPrice("10.00") .setTax("50.00") .setTotalPrice("1060.00") .build(); // Call updateAmount() method. This is mandatory. paymentManager.updateAmount(amount); } } catch (IllegalStateException | NullPointerException e){ e.printStackTrace(); } }
public class CustomSheetPaymentInfo implements Parcelable { private String version; private String merchantId; private String merchantName; private String orderNumber; private PaymentProtocol paymentProtocol; private AddressInPaymentSheet addressInPaymentSheet = AddressInPaymentSheet.DO_NOT_SHOW; private ListallowedCardBrand; private CardInfo cardInfo; private boolean isCardHolderNameRequired = false; private boolean isRecurring = false; private String merchantCountryCode; private CustomSheet customSheet; private Bundle extraPaymentInfo; }
/* * Make user's transaction details. * The merchant app should send CustomSheetPaymentInfo to Samsung Pay via * the applicable Samsung Pay SDK API method for the operation being invoked. */ private CustomSheetPaymentInfo makeCustomSheetPaymentInfo() { ArrayListbList = new ArrayList<>(); // If the supported brand is not specified, all card brands in Samsung Pay are // listed in the Payment Sheet. brandList.add(PaymentManager.Brand.VISA); brandList.add(PaymentManager.Brand.MASTERCARD); brandList.add(PaymentManager.Brand.AMERICANEXPRESS); /* * Make SheetControls you want and add to custom sheet. * Each SheetControl is located in sequence. * There must be a AmountBoxControl and it must be located on last. */ CustomSheet customSheet = new CustomSheet(); customSheet.addControl(makeBillingAddressControl()); customSheet.addControl(makeShippingAddressControl()); customSheet.addControl(makePlainTextControl()); customSheet.addControl(makeShippingMethodSpinnerControl()); // customSheet.addControl(makeInstallmentSpinnerControl()); customSheet.addControl(makeAmountControl()); CustomSheetPaymentInfo customSheetPaymentInfo = new CustomSheetPaymentInfo.Builder() .setMerchantId("123456") .setMerchantName("Sample Merchant") .setOrderNumber("AMZ007MAR") .setPaymentProtocol(CustomSheetPaymentInfo.PaymentProtocol.PROTOCOL_3DS) // Merchant requires billing address from Samsung Pay and //sends the shipping address to Samsung Pay. // Show both billing and shipping address on the payment sheet. .setAddressInPaymentSheet(CustomSheetPaymentInfo.AddressInPaymentSheet. NEED_BILLING_SEND_SHIPPING) .setAllowedCardBrands(bList) .setCardHolderNameEnabled(true) .setRecurringEnabled(false) .setCustomSheet(customSheet) .build(); return customSheetPaymentInfo; }
The result is delivered to CustomSheetTransactionInfoListener, which provides the following events:
The CustomSheetPaymentInfo is payment information used for the current transaction containing amount, shippingAddress, merchantId, merchantName, orderNumber, and paymentProtocol. API methods exclusively available in the onSuccess() callback comprise:
For the Payment Gateway using direct model, the paymentCredential is a JSON object containing encrypted cryptogram which can be passed to the PG. For the Payment Gateway using indirect model like Stripe, it is a JSON object containing reference (card reference – a token ID generated by the PG) and status (AUTHORIZED, PENDING, CHARGED, or REFUNDED). Refer to Sample paymentCredential for details.
/* * CustomSheetTransactionInfoListener is for listening callback events of online * (In-App) custom sheet payment. * This is invoked when card is changed by the user on the custom payment sheet, * and also with the success or failure of online (In-App) payment. */ private PaymentManager.CustomSheetTransactionInfoListener transactionListener = new PaymentManager.CustomSheetTransactionInfoListener() { // This callback is received when the user changes card on the custom payment sheet // in Samsung Pay. @Override public void onCardInfoUpdated(CardInfo selectedCardInfo, CustomSheet customSheet) { /* * Called when the user changes card in Samsung Pay. * Newly selected cardInfo is passed and merchant app can update transaction amount * based on new card (if needed). */ AmountBoxControl amountBoxControl = (AmountBoxControl) customSheet.getSheetControl(AMOUNT_CONTROL_ID); // You can set discount rate. Set discount rate for each card // brand(optional). amountBoxControl.updateValue(PRODUCT_ITEM_ID, 1000); amountBoxControl.updateValue(PRODUCT_TAX_ID, 50); amountBoxControl.updateValue(PRODUCT_SHIPPING_ID, 10); amountBoxControl.updateValue(PRODUCT_FUEL_ID, 0, "Pending"); amountBoxControl.setAmountTotal(1060, AmountConstants.FORMAT_TOTAL_PRICE_ONLY); customSheet.updateControl(amountBoxControl); // Call updateSheet() method. This is mandatory. try { paymentManager.updateSheet(customSheet); } catch (IllegalStateException | NullPointerException e) { e.printStackTrace(); } } /* * This callback is received when the online (In-App) payment transaction is * approved by the user and successfully generates the In-App payload. The * payload can be an encrypted cryptogram (direct In-App payment) or the PG's * token reference ID (indirect In-App payment). */ @Override public void onSuccess(CustomSheetPaymentInfo response, String paymentCredential, Bundle extraPaymentData) { /* * Called when Samsung Pay successfully creates the In-App cryptogram; Merchant app sends * this cryptogram to the merchant server or PG to complete In-App payment */ try { String DPAN = response.getCardInfo().getCardMetaData().getString(SpaySdk.EXTRA_LAST4_DPAN, "Null"); String FPAN = response.getCardInfo().getCardMetaData().getString(SpaySdk.EXTRA_LAST4_FPAN, "Null"); Snackbar.make(fragmentView, " DPAN: " + DPAN + " FPAN: " + FPAN, Snackbar.LENGTH_LONG).setAction(getString(R.string.ok), new View.OnClickListener() { @Override public void onClick(View v) { } }).show(); } catch (NullPointerException e) { e.printStackTrace(); } Toast.makeText(context, "Transaction : onSuccess", Toast.LENGTH_LONG).show(); // You can use PaymentInfo, paymentCredential and extraPaymentData. } // This callback is received when the online payment transaction has failed. @Override public void onFailure(int errorCode, Bundle errorData) { // Called when some error occurred during In-App cryptogram generation. Toast.makeText(context, "Transaction : onFailure : "+ errorCode, Toast.LENGTH_LONG).show(); } }; private void startInAppPayWithCustomSheet() { // PaymentManager.startInAppPayWithCustomSheet to show custom payment sheet. try { Bundle bundle = new Bundle(); bundle.putString(SamsungPay.PARTNER_SERVICE_TYPE, SamsungPay.ServiceType.INAPP_PAYMENT.toString()); final PartnerInfo partnerInfo = new PartnerInfo(serviceId, bundle); paymentManager = new PaymentManager(context, partnerInfo); /* * PaymentManager.startInAppPayWithCustomSheet is Method to request online(In-App) * payment with Samsung Pay. * Merchant app can use this Method to make In-App purchase using Samsung Pay from their * application with custom payment sheet. */ paymentManager.startInAppPayWithCustomSheet(makeCustomSheetPaymentInfo(), transactionListener); } catch (NullPointerException e) { Toast.makeText(context, SHORTTAG + "All mandatory fields cannot be null.", Toast.LENGTH_LONG).show(); e.printStackTrace(); } catch (IllegalStateException e) { Toast.makeText(context, SHORTTAG + "IllegalStateException", Toast.LENGTH_LONG).show(); e.printStackTrace(); } catch (NumberFormatException e) { Toast.makeText(context, SHORTTAG + "Amount values are not valid", Toast.LENGTH_LONG).show(); e.printStackTrace(); } catch (IllegalArgumentException e) { Toast.makeText(context, SHORTTAG + "PaymentInfo values are not valid or all mandatory fields are not set.", Toast.LENGTH_LONG).show(); e.printStackTrace(); } }
For such cases, the merchant app should call updateSheet() with one of the following error codes: